/**
 * @file	TheSOSS.cpp
 * @author  Thomas Fu <thomas.ks.fu@gmail.com>
 * @version 0.0.1
 * @date	03/24/2011	
 *
 * @section License
 * This program is propety of The SOSS and may not be used by any 
 * individual for any purpose at any time without express permission from 
 * Mike Casey, Thomas Fu, Chris Hairfield, Kyle Lamson, Ben Treweek, or Cliff 
 * Warren.
 *
 * @section Arguments
 * ./TheSoss N fs <iDevice> <oDevice> <iChannelOffset> <oChannelOffset> \n
 * N = number of channels \n
 * fs = sample rate (samples per second)\n
 * iDevice = (0 is microphone, 1 is line-in)\n
 * oDevice = (2 is speakers, not sure what the other two are)\n
 * iChannelOffset = input channel offset (default = 0)\n
 * oChannelOffset = output channel offset (default = 0)\n
 *
 * @brief 
 * This file does real-time processing of audio signals on a Macbook Pro. In its
 * current configuration, it reads a single-channel signal from the line-in
 * port, performs some processing, and plays the modified signal over the 
 * computer's speakers.
 */

#include <cmath>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include "IIRFilter.h"
#include "IODataTypes.h"
#include "TubeScreamer.h"
#include "RtAudio.h"
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>

//	Namespace for Linear Algebra functions from the Eigen Library
using namespace Eigen;

//	Standard namespace
using namespace std;

/** Constant to specify format for Real Time Audio data */
#define FORMAT RTAUDIO_FLOAT64

/* * * * * * * * * * * * * * * * * * ENUMS * * * * * * * * * * * * * * * * * */

/** Enumerated type that specifies the device to read the input stream from */
enum inputDevice { MICROPHONE = 0, LINEIN = 1 };

/** Enumerated type that specifies the device to write the output stream to */
enum outputDevice { SPEAKERS = 2 };


/* * * * * * * * * * * * * * * * * CONSTANTS * * * * * * * * * * * * * * * * */

/** The number of audio channels (1 for mono, 2 for stereo) */
#define NUM_CHANNELS 1

/** Sampling frequency (in Hertz) of the input and output channels */
#define SAMPLING_FREQUENCY 44100

/** 
 * The device to be used for audio input (in the future this might not be a 
 * constant
 */
#define INPUT_DEVICE LINEIN

/** 
 * The device to be used for audio output (there is only one option 
 * currently) 
 */
#define OUTPUT_DEVICE SPEAKERS

/** Number of samples per channel per buffer */
#define NUM_BUFFER_FRAMES 64


/* * * * * * * * * * * * * * * FUNCTION PROTOTYPES * * * * * * * * * * * * * */

int ioOps(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, 
					double streamTime, RtAudioStreamStatus status, void *data);
int main(int argc, char *argv[]);


/* * * * * * * * * * * * * * * FUNCTION DEFINITIONS * * * * * * * * * * * * * */

/**
 * Primary function responsible for generating an output buffer from an input
 * buffer. Interfaces with the RTAudio class and provides a framework to process
 * input/output signals. By default, just copies the input buffer directly to
 * the output buffer.
 *
 * @param outputBuffer The buffer of data to be written to the output of the 
 * program (currently the speakers of the computer)
 *
 * @param inputBuffer The buffer of data read from the input to the program
 * (either the line-in port or the built-in microphone)
 *
 * @param nBufferFrames The number of frames (data samples) in inputBuffer and 
 * outputBuffer
 *
 * @param streamTime The time since the stream was opened at the first frame of
 * the buffer
 *
 * @param status A variable containing information about the status of the
 * stream. If stream is false, then everything is normal
 *
 * @param data A variable containing a pointer to any other data needed for this
 * function (including a pointer to any callback functions needed for audio 
 * processing and any data required by these functions)
 *
 * @return An integer encoding information about the successful execution of 
 * this function. A zero indicates successful execution with no problems.
 */
int ioOps(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, 
					double streamTime, RtAudioStreamStatus status, void *data)
{
	// More accurate (or at least consistent) way of keeping track of the time
	// at the start of the buffer than streamTime
	static unsigned int bufferStartTime = 0;
	
	// Checks to ensure the buffers have been filled correctly
	if ( status ) 
		cout << "Stream over/underflow detected." << endl;
	
	// Extract the effect to process the input data buffer 
	Effect *effect = ((ioData*)data)->effect;
	
	// If no effect is specified, write the input buffer straight to the output 
	// buffer
	if(&effect == NULL)
	{
		unsigned int *bytes = ((ioData*)data)->bytes;
		memcpy( outputBuffer, inputBuffer, *bytes);
		return 0;
	}
	
	// Create struct of data to be passed to processing function 
	processData pData;
	pData.bufferStartTime = bufferStartTime;
	pData.bytes = ((ioData*)data)->bytes;
	pData.nBufferFrames = nBufferFrames;
	
	// Increment bufferStartTime to be correct for next call to ioOps
	bufferStartTime += nBufferFrames / ((ioData*)data)->fs;
	
	// Process input data to obtain output data
	return effect->processSound(outputBuffer, inputBuffer, &pData);
}

/**
 * The main function responsible for execution of this program. Eventually this 
 * will do more than just run a single, constant effect from input to output.
 *
 * @param argc A count of the number of arguments entered at the command line 
 * when this program is executed
 *
 * @param argv An array of char*'s that contains the list of arguments passed 
 * into this program when it is executed
 *
 * @return An integer encoding information about the successful execution of 
 * this function. A zero indicates successful execution with no problems.
 */
int main(int argc, char *argv[])
{
	unsigned int channels = NUM_CHANNELS; 
	unsigned int bufferBytes;
	unsigned int oDevice = OUTPUT_DEVICE; 
	unsigned int iDevice = INPUT_DEVICE; 
	unsigned int iOffset = 0, oOffset = 0;
	unsigned int fs = SAMPLING_FREQUENCY;
	
	// Find audio devices
	RtAudio adac;
	if ( adac.getDeviceCount() < 1 ) {
		cout << "\nNo audio devices found!\n";
		exit( 1 );
	}

	// Let RtAudio print messages to stderr.
	adac.showWarnings(true);
	
	// Set the same number of channels for both input and output.
	unsigned int bufferFrames = NUM_BUFFER_FRAMES;
	RtAudio::StreamParameters iParams, oParams;
	iParams.deviceId = iDevice;
	iParams.nChannels = channels;
	iParams.firstChannel = iOffset;
	oParams.deviceId = oDevice;
	oParams.nChannels = channels;
	oParams.firstChannel = oOffset;
	
	RtAudio::StreamOptions options;
	
	try 
	{
		// Create TubeScreamer effect
		TubeScreamer tScream;

		// Create ioData struct to pass information into the ioOps callback
		ioData data;
		data.bytes = &bufferBytes;
		data.effect = (&tScream);
		data.fs = fs;
		
		// Open audio input/ouput streams
		adac.openStream( &oParams, &iParams, FORMAT, fs, &bufferFrames, &ioOps, 
										(void *)&data, &options );
	}
	catch ( RtError& e ) {
		cout << '\n' << e.getMessage() << '\n' << endl;
		exit( 1 );
	}
	
	bufferBytes = bufferFrames * channels * sizeof( double );
	
	// Test RtAudio functionality for reporting latency.
	cout << "\nStream latency = " << adac.getStreamLatency() << " frames" << endl;
	
	try {
		adac.startStream();
		
		char input;
		cout << "\nRunning ... press <enter> to quit (buffer frames = " << bufferFrames << ").\n";
		std::cin.get(input);
		
		// Stop the stream.
		adac.stopStream();
	}
	catch ( RtError& e ) {
		cout << '\n' << e.getMessage() << '\n' << endl;
		goto cleanup;
	}
	
cleanup:
	if ( adac.isStreamOpen() ) adac.closeStream();
	
	return 0;
}
